<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>openGauss Blog  | openGauss/PostgreSQL vacuum full源码解析</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    
    <link rel="shortcut icon" href="https://xzx666.gitee.io/lookeng/img/favicon.ico" type="image/x-icon" />
    <link rel="apple-touch-icon" href="https://xzx666.gitee.io/lookeng/img/apple-touch-icon.png" />

    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    
    <link rel="stylesheet" href="https://xzx666.gitee.io/lookeng/css/commen.css?t=1607593672000" />
    <link rel="stylesheet" href="https://xzx666.gitee.io/lookeng/css/blog.css?t=1607593672000" />
    <link rel="stylesheet" href="https://xzx666.gitee.io/lookeng/css/mobile.css?t=1607593672000" />
    <link rel="stylesheet" href="https://xzx666.gitee.io/lookeng/css/pagination.css?t=1607593672000">
    
    <script>
      var _hmt = _hmt || [];
      (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?34e9f585f29581007941aa1698181871";
        var s = document.getElementsByTagName("script")[0]; 
        s.parentNode.insertBefore(hm, s);
      })();
    </script>
</head>
<body>
  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src='//unpkg.com/valine/dist/Valine.min.js'></script>






<div class="container">
    <div class="columns blog-detail">
        <div class="post_detail">
            <div class="is-child box">
                <div class="breadCrumb"><a href="\zh\">博客/</a></div>
                <div class="blog-detail-header">
                    <h1>openGauss/PostgreSQL vacuum full源码解析</h1>
                    <div class="blog-detail-prop">
                        <img src="https://xzx666.gitee.io/lookeng/img/icon-date.png">
                        <span class="article_right_date">2020-11-20</span>
                        <img src="https://xzx666.gitee.io/lookeng/img/icon-author.png">
                        <span class="article_right_author">数据库架构之美</span>
                        <img src="https://xzx666.gitee.io/lookeng/img/icon-view.png">
                        <span id="/zh/post/shujukujiagouzhimei/opengausspostgresql-vacuum-full%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/" class="leancloud_visitors" data-flag-title="openGauss/PostgreSQL vacuum full源码解析">
                            <i class="leancloud-visitors-count"></i>                     
                        </span>
                    </div>
                    <div class="blog-detail-tags">
                        <img src="https://xzx666.gitee.io/lookeng/img/icon-tag.png">
                        
                        <a href="https://xzx666.gitee.io/lookeng/zh/tags/opengauss/postgresql-vacuum-full%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90">openGauss/PostgreSQL vacuum full源码解析</a>
                        
                    </div>
                </div>
                <div class="content blog-content">
                    

<h1 id="opengauss-postgresql-vacuum-full源码解析-a-name-zh-cn-topic-0291959525-a">openGauss/PostgreSQL vacuum full源码解析<a name="ZH-CN_TOPIC_0291959525"></a></h1>

<p>PostgreSQL和opengauss通过vacuum过程来清理无效的元组，普通的vacuum只清理死元组，释放的页面空闲空间新元组可以使用，但不会反映到磁盘上，而vacuum full会进行重组，保留页面最小大小，释放磁盘空间。</p>

<p>在源码中普通vacuum和vacuum full并不是放在一起的，vacuum full是和cluster过程放在一起的。在pg中vacuum full相关过程在src/backend/commands/cluster.c中，opengauss则在src/gausskernel/optimizer/commands/cluster.cpp中，在cluster.c开头有如下描述：cluster.c CLUSTER a table on an index.  This is now also used for VACUUM FULL.</p>

<p>我整理了一下vacuum full的大体函数结构和调用关系，这里只列出对于一个普通表的vacuum full操作的主要步骤，当然对于分区表和toast表有一些额外的处理，不再列出。</p>

<p><img src="../figures/modb_f632a2a2-17ec-11eb-9cc1-38f9d3cd240d.png" alt="" /></p>

<pre><code>cluster_rel(){

    rebuild_relation(){

        make_new_heap()

        copy_heap_data()

        finsh_heap_swap(){

            swap_relation_files()

            reindex_relation()

            performDeletion()

            RelationMapRemoveMapping()

        }

    }

}
</code></pre>

<p>从上面的流程可以看到vacuum full的过程，主函数是cluster_rel，cluster_rel调用rebuild_relation，所以vacuum full其实是表重建的一个过程，rebuild_relation函数会调用三个大的步骤，make_new_heap-&gt;copy_heap_data-&gt;finsh_heap_swap，先创建新的临时表，然后拷贝数据，最后调用finsh_heap_swap函数完成切换，finsh_heap_swap又分为交换数据文件、重建索引、清理临时表、清除映射关系几个步骤。</p>

<p>从源码中看make_new_heap会调用heap_create_with_catalog()函数进行新的临时表创建，表名为pg_temp_%u。</p>

<p>copy_heap_data函数的过程是：初始化新旧元组，置use_wal标识（copy过程需要记录wal日志），计算freezexid用以剔除死元组，置新表relfrozenxid值，通过优化器评估使用indexscan还是seqscan算法进行扫描，扫描元组，记录日志，加排他锁，写入新堆表。由于代码太长，不再贴进来。</p>

<p>拷贝完数据后调用finsh_heap_swap函数完成切换，第一步swap_relation_files切换表的oid、tablespace等信息。</p>

<p><img src="../figures/modb_f6451c34-17ec-11eb-9cc1-38f9d3cd240d.png" alt="" /></p>

<p>再调用reindex_relation函数进行索引的重建，这里很有意思，因为之前我曾遇到一个现象，在我之前进行对象异常测试的时候，我模拟过几个场景：第一个是在并发创建索引（create index concurrently）的过程中模拟异常，关于并发创建索引的过程可以参考我之前这篇文章：《PostgreSQL在线创建索引你不得不注意的&rdquo;坑&rdquo;》，这样就会留下一个invaild的索引，这个索引是无法使用的，但是在数据表更新的时候这个索引依然会维护，消耗资源，在发生这个情况后我做了一个vacuum full，做完之后竟然惊奇的发现这个索引（索引名没变，索引oid变了）变成了可用的索引，查询也能正常走到索引，看了源码后现在就明白了，vacuum full中途会进行索引的重建。</p>

<p>另外一个场景也很有意思，我模拟索引文件的丢失，通过索引oid找到索引对应的磁盘文件，磁盘文件按照1G一个进行分割，这边模拟删除一个文件，如果使用\di+查看索引的大小你会发现索引也小了这么多，但是正常的那部分索引依旧可用，只影响丢的那部分，但是有一个例外情况，如果丢失的是索引的第一个文件，那么查询如果使用到索引的时候会报出如下文件找不到的报错：ERROR:  could not open file &ldquo;base/16385/74149&rdquo;: No such file or directory，因为一些文件头信息丢失了。</p>

<p>最后就是删除临时表，同时删除临时表的map信息。</p>

                </div>
            </div>
            <div class="box" style="margin-top:15px;">
                <div style="font-size:14px;color:gray"><strong>【免责声明】</strong>本文仅代表作者本人观点，与本网站无关。本网站对文中陈述、观点判断保持中立，不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文仅供读者参考，由此产生的所有法律责任均由读者本人承担。</div>
            </div>
            <div class="post-comment">
                
                  
  <link rel="stylesheet" href="https://xzx666.gitee.io/lookeng/css/custom.css" />
  <div id="vcomments" lang="zh-cn" mess="说点什么吧..."></div>

  <script type="text/javascript">
    const placeholder = document.getElementById("vcomments").getAttribute("mess"),
          lang = document.getElementById("vcomments").getAttribute("lang");
    const  langs = lang == "zh-cn" ? "zh-cn" : "en";
    new Valine({
        el: '#vcomments' ,
        appId: '6wfgavgIRqmpC3hjHqQVtFWF-gzGzoHsz',
        appKey: 'QRqrBDBB0p0YhrGe9IJ169ip',
        avatar:'mm', 
        placeholder: placeholder,
        visitor: true,
		    meta: ['nick','mail','link'],
        lang: langs
    });
  </script>
            </div>
        </div>
    </div>
</div>
<script>
    $(function (){
        var query = new AV.Query('Counter');
        query.equalTo('url', decodeURIComponent('\/zh\/post\/shujukujiagouzhimei\/opengausspostgresql-vacuum-full%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90\/'));
        query.find().then(function (data) {
            if(data.length && (data[0].attributes.author === 'openGauss')){
                var counter = AV.Object.createWithoutData('Counter', data[0].id);
                counter.set('author', '数据库架构之美');
                counter.save();
            }
        })
    })
</script>







<input id="iframeUrl" type="text" style="display: none;" value=https://xzx666.gitee.io/>




<script defer src="https://xzx666.gitee.io/lookengjs/all.js"></script>
<script src="https://xzx666.gitee.io/lookengjs/flexible.js"></script>
<script>
  const hash = window.location.search,
        pageurl = window.location.href;
  var langss = document.querySelector("html").lang === "zh-cn" ? "zh" : "en";
  console.log("hash:",hash,"pageurl:",pageurl,"ttr",pageurl.split(langss + "/")[1])

  if(!document.getElementById("vcomments")) {
    new Valine({
        appId: '6wfgavgIRqmpC3hjHqQVtFWF-gzGzoHsz',
        appKey: 'QRqrBDBB0p0YhrGe9IJ169ip',
        avatar:'mm',
        visitor: true,
		    meta: ['nick','mail','link']
    });
  }
 
  
    


    const url = document.getElementById("iframeUrl").value;

    function observe (el, options, callback) {
      var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
      var observer = new MutationObserver(callback)
      observer.observe(el, options)
    }

    var options = {
        childList: true,
        subtree: true,
        characterData: true
      }
    observe(document.body, options, (records, instance) => {
    const height = document.body.scrollHeight;
    parent.postMessage(height,url);
    })
    if(pageurl.split(langss + "/")[1]) parent.postMessage(pageurl.split(langss + "/")[1],url);
    

    if(document.querySelector("#notFound")) parent.postMessage("我404了",url);

</script>
</body>
</html>
